package com.hunter.lucene.util.annotation.parse;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import com.hunter.lucene.util.annotation.Document;
import com.hunter.lucene.util.annotation.FieldGroup;
import com.hunter.lucene.util.annotation.Fieldable;
import com.hunter.lucene.util.annotation.UniqueField;

/**
 * 
 * @author bastengao
 * 
 */
public class DocumentAnnotationParser {

	/**
	 * 解析在类上的与Document和Fieldabel相关的注解信息，并返回。
	 * 
	 * @param documentAnnotationedClass
	 * @throw IllegalArguentException if clazz has not been presented by
	 *        Document annotation.
	 * @return
	 */
	public DocumentAnnotationWrapper parse(Class<?> documentAnnotationedClass) {
		if (!documentAnnotationedClass.isAnnotationPresent(Document.class)) {
			throw new IllegalArgumentException(
					"the class has no \"Document\" annotation :"
							+ documentAnnotationedClass.getName());
		}
		Document document = documentAnnotationedClass
				.getAnnotation(Document.class);

		DocumentAnnotationWrapper wrapper = new DocumentAnnotationWrapper();

		wrapper.setAnnotationedClass(documentAnnotationedClass);
		wrapper.setDocumentAnnotation(document);
		wrapper
				.setFieldAnnotationWrappers(new ArrayList<FieldableAnnotationWrapper>());
		try {
			stuffField(documentAnnotationedClass, wrapper);
		} catch (IntrospectionException e) {
			e.printStackTrace();
		}
		// TODO
		// fieldableAnnotationWrapper number must not be zero.
		return wrapper;
	}

	private void stuffField(Class<?> clazz,
			DocumentAnnotationWrapper documentWrapper)
			throws IntrospectionException {
		parseField(clazz, documentWrapper);
		parseMethod(clazz, documentWrapper);
	}

	private void parseField(Class<?> clazz,
			DocumentAnnotationWrapper documentWrapper)
			throws IntrospectionException {
		Map<String, PropertyDescriptor> properties = getPropertyMap(clazz);
		Field[] fields = clazz.getDeclaredFields();

		for (Field field : fields) {
			if (field.isAnnotationPresent(Fieldable.class)) {
				FieldableAnnotationWrapper wrapper = new FieldableAnnotationWrapper();
				Fieldable fieldable = field.getAnnotation(Fieldable.class);
				wrapper.setAnnotation(fieldable);

				Class<?> fieldClass = field.getType();
				wrapper.setAnnotationedClass(fieldClass);

				PropertyDescriptor property = properties.get(field.getName());
				Method readMethod = property.getReadMethod();

				if (readMethod == null) {
					throw new IllegalStateException("field : "
							+ field.getName() + " has not a readMethod.");

				} else {
					wrapper.setReadMethod(readMethod);
				}

				documentWrapper.getFieldAnnotationWrappers().add(wrapper);
			}
			if (field.isAnnotationPresent(FieldGroup.class)) {
				FieldGroup data = field.getAnnotation(FieldGroup.class);
				for (Fieldable fieldable : data.value()) {
					FieldableAnnotationWrapper wrapper = new FieldableAnnotationWrapper();
					wrapper.setAnnotation(fieldable);
					wrapper.setAnnotationedClass(field.getType());
					PropertyDescriptor property = properties.get(field
							.getName());
					Method readMethod = property.getReadMethod();

					if (readMethod == null) {
						throw new IllegalStateException("field : "
								+ field.getName()
								+ " has not a readMethod(getter method).");

					} else {
						wrapper.setReadMethod(readMethod);
					}

					documentWrapper.getFieldAnnotationWrappers().add(wrapper);
				}
			}

			if (field.isAnnotationPresent(UniqueField.class)) {
				UniqueField uniqueField = field
						.getAnnotation(UniqueField.class);
				UniqueFieldAnnotationWrapper wrapper = new UniqueFieldAnnotationWrapper();
				wrapper.setUniqueField(uniqueField);
				wrapper.setAnnotationedClass(field.getType());
				PropertyDescriptor property = properties.get(field.getName());
				Method readMethod = property.getReadMethod();
				if (readMethod == null) {
					throw new IllegalStateException("field : "
							+ field.getName()
							+ " has not a readMethod(getter method).");

				} else {
					wrapper.setReadMethod(readMethod);
				}
				documentWrapper.setUniqueField(wrapper);

			}
		}

	}

	public static Map<String, PropertyDescriptor> getPropertyMap(Class<?> clazz)
			throws IntrospectionException {
		Map<String, PropertyDescriptor> map = new HashMap<String, PropertyDescriptor>();
		BeanInfo bean = Introspector.getBeanInfo(clazz);
		for (PropertyDescriptor property : bean.getPropertyDescriptors()) {
			map.put(property.getName(), property);
		}
		return map;
	}

	private void parseMethod(Class<?> clazz,
			DocumentAnnotationWrapper documentWrapper) {
		Method[] methods = clazz.getDeclaredMethods();
		for (Method method : methods) {
			if (method.isAnnotationPresent(Fieldable.class)) {

				FieldableAnnotationWrapper wrapper = new FieldableAnnotationWrapper();

				wrapper.setAnnotation(method.getAnnotation(Fieldable.class));
				wrapper.setAnnotationedClass(method.getReturnType());
				wrapper.setReadMethod(method);

				documentWrapper.getFieldAnnotationWrappers().add(wrapper);

			}

			if (method.isAnnotationPresent(FieldGroup.class)) {
				Class<?>[] params = method.getParameterTypes();
				if (params.length != 0) {
					throw new IllegalStateException(
							"method must has no parameter");
				}

				FieldGroup data = method.getAnnotation(FieldGroup.class);
				for (Fieldable fieldable : data.value()) {
					FieldableAnnotationWrapper wrapper = new FieldableAnnotationWrapper();
					wrapper.setAnnotation(fieldable);
					wrapper.setAnnotationedClass(method.getReturnType());
					wrapper.setReadMethod(method);

					documentWrapper.getFieldAnnotationWrappers().add(wrapper);
				}
			}

			if (method.isAnnotationPresent(UniqueField.class)) {		
				UniqueField uniqueField = method
						.getAnnotation(UniqueField.class);
				UniqueFieldAnnotationWrapper wrapper = new UniqueFieldAnnotationWrapper();
				wrapper.setUniqueField(uniqueField);
				wrapper.setAnnotationedClass(method.getReturnType());
				wrapper.setReadMethod(method);

				documentWrapper.setUniqueField(wrapper);
			}
		}
	}
}
